/*
 * @Author: yangshangchao
 * @Date: 2025-01-23 11:01:54
 * @LastEditors: yangshangchao
 * @Description: 头部注释
 */

// 适用场景
// 订阅外部 store 例如(redux,Zustand德语)
// 订阅浏览器API 例如(online,storage,location)等
// 抽离逻辑，编写自定义hooks
// 服务端渲染支持

// 目标：模拟怎么用这个自定义hook-useStorage
// const [res,setCount] = useStorage('count', 1)
// setCount(2)

import { useSyncExternalStore } from 'react';

export const useStorage = (key: string, initialValue: any) => {
  /**
   * @description: 订阅者函数，接收一个单独的 callback 参数。用来订阅外部数据源的变化，当外部数据源发生变化时，会调用提供的 callback。这将导致 React 重新调用 getSnapshot 并在需要的时候重新渲染组件
   * @return {*} 返回清除订阅的函数
   */
  const subscribe = (callback: () => void) => {
    console.log('subscribe');
    // 订阅浏览器API
    // localStorage发生变化会触发下面这个监听
    // window.addEventListener('storage', callback);
    window.addEventListener('storage', (e) => {
      console.log(e, '触发了');
      callback();
    });
    return () => {
      // 返回取消订阅
      window.removeEventListener('storage', callback);
    };
  };

  /**
   * @description: 快照数据函数，这个快照数据会被传递给组件
   * 在 数据 不变的情况下，重复调用 getSnapshot 必须返回同一个值。如果数据改变，并且返回值也不同了（用 Object.is 比较），React 就会重新渲染组件。
   * @return {*} 返回组件需要的快照数据
   */
  const getSnapshot = () => {
    // return localStorage.getItem(key) || initialValue;
    return localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key)!) : initialValue;
    // 第一次没有setItem，localStorage没有对应的key值。所以返回默认值initialValue
  };

  const res = useSyncExternalStore(subscribe, getSnapshot);
  // setCount(2) 这样用来更新数据，设置一个参数，接收数据
  const updateStorage = (value: any) => {
    localStorage.setItem(key, JSON.stringify(value));
    // 坑1：视图不更新，需要手动触发storage事件
    window.dispatchEvent(new StorageEvent('storage'));
  };

  // 需要返回一个最新的数据还有一个更新这个数据的函数
  return [res, updateStorage];
};

// 坑1：点击存储+1，发现页面没有变化，但是localStorage里面的值已经变了。因为没有触发window.addEventListener('storage'。所以callback没有执行,react组件没有重新渲染。
// 解决：需要手动触发storage事件，window.dispatchEvent(new StorageEvent('storage'));

// 最终效果演示：
// 值的持久化：一直刷新页面，页面的值一直是storage的值
// 跨标签页同步：数据跨标签页同步更新【因为读的是同源的localStorage】
